package com.tsd.core.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;

import javax.script.*;
import java.util.*;

/**
 * 脚本工具类
 *
 * @author Hillpool
 */
public class ScriptUtil {
    private ThreadLocal<ScriptEngine> engineHolder;
    Bindings bindings;

    public ScriptUtil() {
        init();
    }

    public static ScriptUtil getInstance() {
        return new ScriptUtil();
    }

    private void init() {
        engineHolder = ThreadLocal.withInitial(() -> new ScriptEngineManager().getEngineByName("nashorn"));
        ScriptEngine engine = engineHolder.get();
        bindings = engine.createBindings();
    }

    public Bindings getBindings() {
        return bindings;
    }

    public CompiledScript getCompile(String script) throws ScriptException {
        ScriptEngine engine = engineHolder.get();
        Compilable compilable = (Compilable) engine;
        return compilable.compile(script);
    }

    public double calDouble1(CompiledScript compiledScript, Map<String, Object> params) throws ScriptException {
        bindings.put("params", params);
        Object result = compiledScript.eval(bindings);
        return (Double) result;
    }

    /**
     * 计算双精度
     *
     * @param script
     * @param params
     * @return
     * @throws ScriptException
     */
    public double calDouble(String script, Map<String, Object> params) throws ScriptException {
        ScriptEngine engine = engineHolder.get();
        Compilable compilable = (Compilable) engine;
        Bindings bindings = engine.createBindings();
        CompiledScript JSFunction = compilable.compile(script);
        bindings.put("params", params);
        Object result = JSFunction.eval(bindings);
        return (Double) result;
    }

    private Map<String, Object> changeMapNameUnderline(Map<String, Object> params) throws Exception {
        Map<String, Object> result = new HashMap<String, Object>();
        if (params != null) {
            Set<String> keys = params.keySet();
            for (String key : keys) {
                if (key.contains("feeMeter") || key.contains("function")) {
                    result.put(key, params.get(key));
                } else {
                    String name = key;
                    if (!key.contains("_")) {
                        name = HlpUtils.camel2Underline(key);
                    }
                    result.put(name, params.get(key));
                }
            }
        }
        return result;
    }

    public void testScriptFormulaResult(String script) throws Exception {
        Map<String, Object> params = new HashMap<String, Object>();
        if (!HlpUtils.isEmpty(script)) {
            JSONArray array = JSON.parseArray(script);
            if (array.size() > 0) {
                for (int i = 0; i < array.size(); i++) {
                    if (array.get(i) instanceof JSONObject) {
                        JSONObject object = array.getJSONObject(i);
                        if (object.containsKey("id")) {
                            params.put(object.getString("value"), 1);
                        }
                    }
                }
            }
        }
        String runScript = this.getRunScript(script);
        if (runScript != null) {
            getScriptFormulaResult(runScript, params);
        }
    }

    /**
     * 将脚本字符串转为js脚本
     *
     * @param script
     * @return
     */
    private String getRunScript(String script) {
        if (HlpUtils.isEmpty(script)) {
            return null;
        }
        JSONArray array = JSON.parseArray(script);
        if (HlpUtils.isEmptyList(array)) {
            return null;
        }
        List<String> valueList = new ArrayList<>();
        for (int i = 0; i < array.size(); i++) {
            JSONObject object = array.getJSONObject(i);
            if (object.containsKey("id")) {
                valueList.add("params." + object.getString("value"));
            } else {
                valueList.add(object.getString("value"));
            }
        }
        return String.format("function calc(params) { return %s; } calc(params)", StringUtils.join(valueList, ""));
    }

    public Object getScriptFormulaResult(String srcipt, Map<String, Object> params) throws Exception {
//        params = this.changeMapNameUnderline(params);
        CompiledScript script = this.getCompile(srcipt);
        this.bindings.put("params", params);
        Object result = script.eval(this.bindings);
        return result == null || Double.isNaN(Double.parseDouble(result.toString())) ? 0 : result;
    }

    public static void main(String[] args) {
        ScriptUtil scriptUtil = new ScriptUtil();
        Map<String, Object> params = new HashMap<>();
        params.put("a", 123);
        params.put("b", 3.4);
        //定義函數並調用
        String script = "function add(o){return o.a+o.b} add(o)";
        try {
            long s = Calendar.getInstance().getTimeInMillis();
            CompiledScript compiledScript = scriptUtil.getCompile(script);
            for (int i = 0; i < 10000; i++) {
                Double d = scriptUtil.calDouble1(compiledScript, params);
            }
            long e = Calendar.getInstance().getTimeInMillis();
            System.out.println(e - s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main1(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        // 得到所有的引擎工厂
        List<ScriptEngineFactory> list = manager.getEngineFactories();
        for (ScriptEngineFactory factory : list) {
            System.out.println("支持的脚本语言" + factory.getNames());
            // 支持的脚本语言[nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
        }

        // 得到常用的javascript脚本
        ScriptEngine engine = manager.getEngineByName("Nashorn");

        try {
            System.out.println(engine.getFactory().getParameter(ScriptEngine.LANGUAGE));
            System.out.println(engine.getFactory().getParameter("THREADING"));
            System.out.println(engine.eval("((1+1)*9+9.6)*(8+9-8)"));
            // 执行脚本
            System.out.println(engine.eval("2+3*2"));  // 8
            System.out.println(engine.eval("(true||false)&&true"));  //true
            // 和脚本交互
            engine.put("name", "123456");
            engine.eval("var out = '';" +
                    "for(i=0;i<name.length;i++){" +
                    "    out = name.charAt(i)+ out}");
            String name = (String) engine.get("out");
            System.out.println("name:" + name);
            System.out.println(engine.eval(""));


            Map<String, Object> params = new HashMap<>();
            params.put("a", 123);
            params.put("b", 3.4);
            Compilable compilable = (Compilable) engine;
            Bindings bindings = engine.createBindings(); //Local級別的Binding
            String script = "function add(o){return o.a+o.b} add(o)"; //定義函數並調用
            CompiledScript JSFunction = compilable.compile(script); //解析編譯腳本函數
            bindings.put("o", params); //通過Bindings加入參數
            Object result = JSFunction.eval(bindings);
            System.out.println(result); //調用緩存著的腳本函數對象，Bindings作為參數容器傳入
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
